<?php
/**
 * Данный класс использует свойства и методы стандартного класса SplFileObject и представляет собой адаптер для обмена данными между "движком" гостевой книги gbook.php и двумя CSV-файлами, gb_messages и gb_comments, использующимися в качестве хранилища данных 
 *  
 * Класс содержит закрытые свойства, открытые и закрытые методы
 *
 * @author Massa
 */
class GbCSV {

	/**
	 * Получает объект-хранилище постов, класса GbCSV 
     *
	 * @var GbCSV
 	 */
	private $_gbcoms;

	/**
	 * Получает объект-хранилище комментариев, класса GbCSV 
     *
	 * @var GbCSV
 	 */
	private $_gbmes;

	/**
	 * Получает объект-временный файл, класса GbCSV 
     *
	 * @var GbCSV
 	 */
	private $_tmpf;
	
	/**
     * Конструктор объекта класса
     *
	 * Объект представляет собой два открытых файла, gb_messages и gb_comments, служащие для хранения, соответственно, постов и комментариев 
	 * Файлы создаются, как объекты стандартного класса SplFileObject, благодаря чему наследуют все полезные методы и константы данного класса
     */
	public function __construct() {

		$dir = __DIR__.DIRECTORY_SEPARATOR."csv";

// Создаются или открываются два csv-файла

		$this->_gbmes = new SplFileObject ($dir.DIRECTORY_SEPARATOR."gb_messages.csv","a+");

		$this->_gbcoms = new SplFileObject ($dir.DIRECTORY_SEPARATOR."gb_comments.csv","a+");

// Парсинг строк будет осуществляться в формате "csv"

		$this->_gbmes->setFlags(SplFileObject::READ_CSV);

		$this->_gbcoms->setFlags(SplFileObject::READ_CSV);

// Задается разделитель полей "csv" - символ "|"

		$this->_gbmes->setCsvControl("|");

		$this->_gbcoms->setCsvControl("|");

	}

	/**
	 * Деструктор объекта класса
     *
	 * Объект уничтожается путем присвоения значения NULL содержащему его свойству
 	 */	
	public function __destruct(){
		$this->_gbmes = null;
		$this->_gbcoms = null;
	}
	
	/**
	 * Метод добавления поста в файл
     *
	 * Принимает имя пользователя ($name) и текст сообщения ($post) 
	 *
	 * @param string $name
	 * @param string $post
 	 */
	public function postSave($name, $post) {

// Обработка входных данных: экранирование спецсимволов, очистка тэгов, подрезка и удаление пробелов
// задание TIMESTAMP

		$name = addslashes(trim(strip_tags($name)));
		$post = addslashes(trim(strip_tags($post)));
		$dt = time();

// Создаем пустой массив

		$fields = array();

// Обращаемся к последней записи хранилища, задав запредельное значение методу SplFileObject::seek()

		$this->_gbmes->seek(1000000000);

// Получаем порядковый номер последней записи из файла, используя тернарный оператор присваивания:
// для пустого, т.е. не содержащего сообщений, файла, и для файла с сообщениями

		$newID = ($this->_gbmes->key()) ? ($this->_gbmes->key() - 1) : $this->_gbmes->key();

// Устанавливаем указатель в конец файла

		$this->_gbmes->seek($newID);

// Получаем значение первого поля (id) последней записи и увеличиваем его на 1

		$curRec = $this->_gbmes->current();

 
		$newID = (int)$curRec[0] + 1;

// Записываем в файл новую запись

		$result = $this->_gbmes->fwrite($newID."|".$name."|\"".$post."\"|".$dt."\n");

// В случае ошибки...

		if(!$result)
			die("Не удалось добавить сообщение!");

	}
	
	/**
	 * Метод обновления поста в файле
     *
	 * Принимает идентификатор поста ($id) и текст сообщения ($post) 
	 * Возвращает положительный результат запроса на обновление записи
     * Механизм обновления записи: клонируется текущее состояние файла постов во временный файл,
     * очищается файл постов, построчно копируются записи из временного файла в файл постов,
     * при этом, измененная запись создается заново 
	 *
	 * @param integer $id
	 * @param string $post
	 * @return bool
 	 */
	public function postUpdate($id, $post) {

// Обработка входных данных: приведение id к положительному целому типу, 
// экранирование спецсимволов, очистка тэгов, подрезка и удаление пробелов

		$id = abs((int)$id);
		$post = addslashes(trim(strip_tags($post)));

// Закрытый встроенный метод создания и открытия временного файла 			
		
		$this->openTmpFile("messages");

// Очистка файла хранения постов методом SplFileObject::ftruncate()

		$this->_gbmes->ftruncate(0);

// Создаем пустой массив

		$fields = array();

		/* В цикле перебираем строки временного файла
 		 * Условие цикла - конец (eof) файла
   		 * Если первое поле не пустое и его значение равно id поста,
   		 *   заполняем массив значениями и сохраняем новую запись точно на место старой
   		 * Итерируем цикл методом SplFileObject::next()
		 * Сохраняем записи методом SplFileObject::fputcsv()
 		 */

		while(!$this->_tmpf->eof()) {
			$testID = $this->_tmpf->current();

			$curid = (int)$testID[0];
			if($curid) {
			$curname = $testID[1];
			$curpost = $testID[2];
			$curdt = (int)$testID[3];
			}
			
			if($testID[0]){
				if($curid !== $id) 
					$this->_gbmes->fwrite($curid."|".$curname."|\"".$curpost."\"|".$curdt."\n");
				else 
					$this->_gbmes->fwrite($id."|".$curname."|\"".$post."\"|".$curdt."\n");

				$this->_tmpf->next();
			}
		}

// Закрываем временный файл 

		$this->_tmpf = null;

// Закрытый встроенный метод удаления временного файла 

		$this->closeTmpFile();

		return true;

	}
	
	/**
	 * Метод получения всех постов из файла
     *
     * Используются унаследованные методы класса SplFileObject 
	 *
	 * @return array
 	 */
	public function getAllPosts() {

// Создаем пустой массив

		$postArray = array();

// Устанавливаем указатель в начало файла

		$this->_gbmes->rewind();

		$strNum = 0;

		/* В цикле перебираем строки файла
 		 * Условие цикла - конец (eof) файла
   		 * Индексируем, ассоциируем и заполняем массив значениями
   		 * Итерируем цикл методом SplFileObject::next()
   		 * Если запись поста из файла не раскладывается на 4 поля, значит файл поврежден
 		 */
		while(!$this->_gbmes->eof()) {

			$curStr = $this->_gbmes->current();
		
			$postArray[$strNum] = array(
				'datetime' => array_pop($curStr),
				'post' => array_pop($curStr),
				'name' => array_pop($curStr),
				'id' => array_pop($curStr)
			);

			if(!$postArray[$strNum])
				die("Файл gb_messages.csv поврежден!");

			$this->_gbmes->next(); 

			$strNum++;

		}

// Удаление из массива паразитной первой строки

		array_pop($postArray);	

// Сортировка массива в порядке, обратном поступлению сообщений постов

		$postArray = array_reverse($postArray);

		return $postArray;

	}

	/**
	 * Метод получения выбранного поста из файла
     *
	 * Принимает идентификатор поста ($id) 
	 * Возвращает результат запроса в виде массива полей поста или false - при неудаче
     * Организация цикла перебора записей в файле, выборки записи и заполнения массива -
     *   аналогично методу getAllPosts(), но выбирается только одна запись, поэтому 
	 *   результирующий массив имеет только один родительский индекс - [0] 
	 *
	 * @param integer $id
	 * @return array|false
 	 */
	public function getPost($id) {
		$id = abs((int)$id);

		$postArray = array();

		$this->_gbmes->rewind();

		while(!$this->_gbmes->eof()) {
			$curStr = $this->_gbmes->current();

			if((int)$curStr[0] == $id) {
				$postArray[0] = 
					array(
						'datetime' => array_pop($curStr),
						'post' => array_pop($curStr),
						'name' => array_pop($curStr),
						'id' => array_pop($curStr)
					);

				if(!$postArray[0])
						die("Файл gb_messages.csv поврежден!");

				return $postArray;
			}
			$this->_gbmes->next();
		}
		return false;
	}
	
	/**
	 * Метод получения всех комментариев к выбранному посту из файла
     *
	 * Принимает идентификатор поста ($id) 
	 * Возвращает результат запроса в виде массива полей комментариев
	 * Индексация, ассоциация и заполнение массива - аналогично методу getAllPosts()
     * Если запись комментария из файла не раскладывается на 5 полей, значит файл поврежден
	 *
	 * @param integer $pid
	 * @return array
 	 */
	public function getAllComments($id) {
		$id = abs((int)$id);

		$commArray = array();

		$this->_gbcoms->rewind();

			$strNum = 0;

			while(!$this->_gbcoms->eof()) {

				$curStr = $this->_gbcoms->current();
		
				if((int)$curStr[0]) {
					$pid = (int)$curStr[3];
						if($pid == $id) {
							$commArray[$strNum] = array(
								'datetime' => array_pop($curStr),
								'pid' => array_pop($curStr),		
								'comment' => array_pop($curStr),
								'name' => array_pop($curStr),
								'id' => array_pop($curStr)
							);

							if(!$commArray[$strNum])
								die("Файл gb_comments.csv поврежден!");

							$strNum++;					
						}
				}
				$this->_gbcoms->next();
			}

		return $commArray;

	}
	
	/**
	 * Метод удаления выбранного поста из файла
     *
	 * Принимает идентификатор поста ($id) 
	 * Возвращает положительный результат запроса на удаление записи
     * Механизм удаления записи: клонируется текущее состояние файла постов во временный файл,
     *   очищается файл постов, построчно копируются записи из временного файла в файл постов,
     *   при этом, удаляемая запись пропускается 
	 *
	 * @param integer $id
	 * @return bool
 	 */
	public function postDelete($id){

// Приведение id к положительному целому типу 

		$id = abs((int)$id);

// Закрытый встроенный метод создания и открытия временного файла 			
		
		$this->openTmpFile("messages");

// Очистка файла хранения постов методом SplFileObject::ftruncate()

		$this->_gbmes->ftruncate(0);

		/* В цикле перебираем строки временного файла
 		 * Условие цикла - конец (eof) файла
   		 * Если первое поле не пустое и его значение равно id поста,
   		 *   не заносим эту запись в файл
   		 * Итерируем цикл методом SplFileObject::next()
		 * Сохраняем записи методом SplFileObject::fputcsv()
 		 */
		while(!$this->_tmpf->eof()) {
			$testID = $this->_tmpf->current();
			if($testID[0] && (int)$testID[0] !== $id) {
				$testID[2] = "\"".$testID[2]."\"";
				$finStr = implode("|", $testID)."\n";
				$this->_gbmes->fwrite($finStr);
			}
			$this->_tmpf->next();
		}

// Закрываем временный файл 

		$this->_tmpf = null;

// Закрытый встроенный метод удаления временного файла

		$this->closeTmpFile();

		return true;

	}
	
	/**
	 * Метод добавления комментария к выбранному посту в файле
     *
	 * Принимает имя пользователя ($name), текст комментария ($comment) и идентификатор поста ($pid)
	 *
	 * @param string $name
	 * @param string $comment
	 * @param integer $pid
 	 */
	public function commentAdd($name, $comment, $pid) {

// Обработка входных данных: экранирование спецсимволов, очистка тэгов, подрезка и удаление пробелов,
// приведение к целому положительному типу, задание TIMESTAMP

		$pid = abs((int)$pid);
		$name = addslashes(trim(strip_tags($name)));
		$comment = addslashes(trim(strip_tags($comment)));	
		$dt = time();

// Создаем пустой массив

		$fields = array();

// Обращаемся к последней записи хранилища, задав запредельное значение методу SplFileObject::seek()

		$this->_gbcoms->seek(1000000000);

// Получаем порядковый номер последней записи из файла, используя тернарный оператор присваивания:
// для пустого, т.е. не содержащего комментариев, файла, и для файла с комментариями

		$newID = ($this->_gbcoms->key()) ? ($this->_gbcoms->key() - 1) : $this->_gbcoms->key();

// Устанавливаем указатель в конец файла

		$this->_gbcoms->seek($newID);

// Получаем значение первого поля (id) последней записи и увеличиваем его на 1

		$curRec = $this->_gbcoms->current();
 
		$newID = (int)$curRec[0] + 1;

// Записываем в файл новую запись

		$result = $this->_gbcoms->fwrite($newID."|".$name."|"."\"".$comment."\""."|".$pid."|".$dt."\n");

// В случае ошибки...

		if(!$result)
			die("Не удалось добавить комментарий!");

	}
	
	/**
	 * Метод удаления комментария/комментариев к выбранному посту в файле
     *
	 * Допускает удаление выбранного комментария или всех комментариев к заданному посту
	 * Принимает идентификатор комментария ($id) и идентификатор поста ($pid) 
	 * Возвращает положительный результат запроса на удаление записи
     * Механизм удаления записи: клонируется текущее состояние файла комментариев во временный файл,
     *   очищается файл комментариев, построчно копируются записи из временного файла в файл
	 *   комментариев, при этом, удаляемая запись/записи пропускается
	 *
	 * @param integer $id
	 * @param integer $pid
	 * @return bool
 	 */
	public function commentRemove($id, $pid=0) {

// Приведение к целому положительному типу

		$id = abs((int)$id);
		$pid = abs((int)$pid);

// Закрытый встроенный метод создания и открытия временного файла 			
		
		$this->openTmpFile("comments");

// Очистка файла хранения комментариев методом SplFileObject::ftruncate()

		$this->_gbcoms->ftruncate(0);

// Если удаляем только один комментарий

		if (!$pid){
			while(!$this->_tmpf->eof()) {
				$testID = $this->_tmpf->current();
				if($testID[0] && (int)$testID[0] !== $id) {
					$testID[2] = "\"".$testID[2]."\"";
					$finStr = implode("|", $testID)."\n";
					$this->_gbcoms->fwrite($finStr);
				}
				$this->_tmpf->next();
			}

// Если удаляем все комментарии к посту

		} else {
			while(!$this->_tmpf->eof()) {
				$testID = $this->_tmpf->current();
				if(isset($testID[3]) && $testID[3] && ((int)$testID[3] !== $pid)) {
					$testID[2] = "\"".$testID[2]."\"";
					$finStr = implode("|", $testID)."\n";
					$this->_gbcoms->fwrite($finStr);
				}
				$this->_tmpf->next();
			}
		}

// Закрываем временный файл 

		$this->_tmpf = null;

// Закрытый встроенный метод удаления временного файла

		$this->closeTmpFile();

		return true;
	}
	
	/**
	 * Метод создания и открытия временного файла
     *
	 * Используется при удалении постов/комментариев и редактировании постов
	 * Принимает тип файла-хранилища: посты или комментарии
	 *
	 * @param string $type
 	 */
	private function openTmpFile($type) {

// Селектор типа файла

		switch($type) {
			case "messages":
				$filestr = "gb_messages.csv";
				break;
			case "comments":
				$filestr = "gb_comments.csv";
				break;		
		}

// Задание субдиректории

		$dir = __DIR__.DIRECTORY_SEPARATOR."csv";

// Клонирование файла-хранилища во временный файл

		if(!copy($dir.DIRECTORY_SEPARATOR.$filestr, $dir.DIRECTORY_SEPARATOR."_tmpfile"))
			die("Не удалось создать временный файл!");

// Создается временный csv-файл

		$this->_tmpf = new SplFileObject ($dir.DIRECTORY_SEPARATOR."_tmpfile","a+");

// Парсинг строк будет осуществляться в формате "csv"

		$this->_tmpf->setFlags(SplFileObject::READ_CSV);

// Задается разделитель полей "csv" - символ "|"

		$this->_tmpf->setCsvControl("|");

	}

	/**
	 * Метод удаления временного файла
     *
	 * Используется при удалении постов/комментариев и редактировании постов
 	 */
	private function closeTmpFile() {
		$dir = __DIR__.DIRECTORY_SEPARATOR."csv";
		if(!unlink($dir.DIRECTORY_SEPARATOR."_tmpfile"))
			die("Не удалось удалить временный файл!");
	}

}

?>
